package com.yupi.yupicturebackend.manager.auth;

import cn.dev33.satoken.stp.StpInterface;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import cn.hutool.json.JSONUtil;
import com.yupi.yupicturebackend.constant.UserConstant;
import com.yupi.yupicturebackend.exception.BusinessException;
import com.yupi.yupicturebackend.exception.ErrorCode;
import com.yupi.yupicturebackend.manager.auth.constant.SpaceUserPermissionConstant;
import com.yupi.yupicturebackend.manager.auth.model.SpaceUserAuthConfig;
import com.yupi.yupicturebackend.manager.auth.model.SpaceUserAuthContext;
import com.yupi.yupicturebackend.model.entity.Picture;
import com.yupi.yupicturebackend.model.entity.Space;
import com.yupi.yupicturebackend.model.entity.SpaceUser;
import com.yupi.yupicturebackend.model.entity.User;
import com.yupi.yupicturebackend.model.enums.SpaceRoleEnum;
import com.yupi.yupicturebackend.model.enums.SpaceTypeEnum;
import com.yupi.yupicturebackend.service.PictureService;
import com.yupi.yupicturebackend.service.SpaceService;
import com.yupi.yupicturebackend.service.SpaceUserService;
import com.yupi.yupicturebackend.service.UserService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * 自定义权限认证接口实现类
 */
@Component
public class StpInterfaceImpl implements StpInterface {

    @Resource
    private SpaceUserAuthManager spaceUserAuthManager;

    @Resource
    private SpaceUserService spaceUserService;

    @Resource
    private PictureService pictureService;

    @Resource
    private UserService userService;

    @Resource
    private SpaceService spaceService;

    @Value("${server.servlet.context-path")
    private String contextPath;

    /**
     * 返回一个账号所拥有的权限集合
     * @param loginId
     * @param loginType
     * @return
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // 判断 loginType, 仅对类型为 space 进行权限校验
        if (!StpKit.SPACE_TYPE.equals(loginType)) {
            return Collections.emptyList();
        }
        // 管理员权限，表示权限认证通过
        List<String> ADMIN_PERMISSIONS = spaceUserAuthManager.getPermissionsByRole(SpaceRoleEnum.ADMIN.getValue());
        // 获取上下文对象
        SpaceUserAuthContext authContext = getAuthContextByRequest();
        // 判断上下文对象所有字段是否为空，是则表示查询公共图库，可以通过
        if (isAllFieldsNull(authContext)) {
            return ADMIN_PERMISSIONS;
        }
        // 获取 userId 校验登录状态：通过 loginId 获取当前登录用户信息。如果用户未登录，抛出未授权异常；否则获取用户的唯一标识 userId，用于后续权限判断
        User loginUser = (User) StpKit.SPACE.getSessionByLoginId(loginId).get(UserConstant.USER_LOGIN_STATE);
        if (loginUser == null) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "未登录");
        }
        Long userId = loginUser.getId();
        // 优先从上下文中或 SpaceUser 对象
        // 如果上下文中存在 SpaceUser 对象，直接根据其角色获取权限码列表。
        SpaceUser spaceUser = authContext.getSpaceUser();
        if (spaceUser != null) {
            return spaceUserAuthManager.getPermissionsByRole(spaceUser.getSpaceRole());
        }
        // 如果上下文中没有 SpaceUser 对象，有 spaceUserId，必然是团队空间，则从数据库中查询 SpaceUser 对象，并获取其角色获取权限码列表。
        Long spaceUserId = authContext.getSpaceUserId();
        if(spaceUserId != null){
            spaceUser = spaceUserService.getById(spaceUserId);
            if (spaceUser == null) {
                throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "空间用户不存在");
            }
            // 取出当前登录用户对应的SpaceUser
            // 校验当前登录用户是否属于该空间，如果不是，返回空权限列表。
            SpaceUser loginSpaceUser = spaceUserService.lambdaQuery()
                    .eq(SpaceUser::getUserId, userId)
                    .eq(SpaceUser::getSpaceId, spaceUser.getSpaceId())
                    .one();
            if (loginSpaceUser == null) {
                return new ArrayList<>();
            }
            // 否则，根据登录用户在该空间的角色，返回相应的权限码列表。
            return spaceUserAuthManager.getPermissionsByRole(loginSpaceUser.getSpaceRole());
        }
        // 通过 spaceId 和 userId 获取 SpaceUser 对象，并获取其角色获取权限码列表。
        // 如果 spaceId 不存在：使用 pictureId 查询图片信息，并通过图片的 spaceId 继续判断权限；如果 pictureId 和 spaceId 均为空，默认视为管理员权限。
        Long spaceId = authContext.getSpaceId();
        if (spaceId == null) {
            // 没有 spaceId 获取 pictureId,通过pictureId 获取 picture 和 Space对象
            Long pictureId = authContext.getPictureId();
            // 如果 pictureId 为空，默认返回管理员权限
            if (pictureId == null) {
                return ADMIN_PERMISSIONS;
            }
            Picture picture = pictureService.lambdaQuery()
                    .eq(Picture::getId, pictureId)
                    .eq(Picture::getUserId, userId)
                    .one();
            if (picture == null){
                throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "图片不存在");
            }
            spaceId = picture.getSpaceId();
            // 公共图库，仅管理员和或本人可以操作
            if (spaceId == null){
                if (picture.getUserId().equals(userId) || userService.isAdmin(loginUser)){
                    return ADMIN_PERMISSIONS;
                } else {
                    // 不是自己的图片，仅可以查看
                    return Collections.singletonList(SpaceUserPermissionConstant.PICTURE_VIEW);
                }
            }
        }
        // spaceId 不为空，获取 space 对象
        Space space = spaceService.getById(spaceId);
        if (space == null){
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "空间不存在");
        }
        // 根据 space 类型判断权限
        if (space.getSpaceType() == SpaceTypeEnum.PRIVATE.getValue()){
            // 私有空间，仅本人和管理员有权限
            if (space.getUserId().equals(userId) || userService.isAdmin(loginUser)){
                return ADMIN_PERMISSIONS;
            }  else {
               return Collections.emptyList();
            }
        } else {
            // 团队空间，根据 SpaceUser 获取角色和权限
            spaceUser = spaceUserService.lambdaQuery()
                    .eq(SpaceUser::getUserId, userId)
                    .eq(SpaceUser::getSpaceId, spaceId)
                    .one();
            if (spaceUser == null){
                return Collections.emptyList();
            }
            return spaceUserAuthManager.getPermissionsByRole(spaceUser.getSpaceRole());
        }
    }

    private boolean isAllFieldsNull(Object object) {
        if (object == null) {
            return true; // 对象本身为空
        }
        // 获取所有字段并判断是否所有字段为空
        return Arrays.stream(ReflectUtil.getFields(object.getClass()))
                // 获取字段值
                .map(field -> ReflectUtil.getFieldValue(object, field))
                // 检查所有字段是否为空
                .allMatch(ObjUtil::isNull);
    }

    /**
     * 返回一个账号所拥有的角色集合
     * @param o
     * @param s
     * @return
     */
    @Override
    public List<String> getRoleList(Object o, String s) {
        return null;
    }

    /**
     * 从请求中获取上下文对象
     * @return
     */
    private SpaceUserAuthContext getAuthContextByRequest(){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        String contentType = request.getHeader(Header.CONTENT_TYPE.getValue());
        // 兼容 get 和 post 操作
        SpaceUserAuthContext authRequest;
        if (ContentType.JSON.getValue().equals(contentType)) {
            String body = ServletUtil.getBody(request);
            // HttpServletRequest 的 body 值是个流，只支持读取一次，读完就没了！所以为了解决这个问题，我们还要在 config 包下自定义请求包装类和请求包装类过滤器。
            authRequest = JSONUtil.toBean(body, SpaceUserAuthContext.class);
        }else {
            Map<String, String> paramMap = ServletUtil.getParamMap(request);
            authRequest = BeanUtil.toBean(paramMap, SpaceUserAuthContext.class);
        }
        // 根据请求路径区分 id 字段的含义
        Long id = authRequest.getId();
        if (ObjUtil.isNotNull(id)) {
            String requestURI = request.getRequestURI();
            String partURI = requestURI.replace(contextPath + "/", "");
            String moduleName = StrUtil.subBefore(partURI, "/", false);
            switch (moduleName) {
                case "picture":
                    authRequest.setPictureId(id);
                    break;
                case "spaceUser":
                    authRequest.setSpaceUserId(id);
                    break;
                case "space":
                    authRequest.setSpaceId(id);
                    break;
                default:
            }
        }
        return authRequest;
    }

}
